package com.unittec.zk.provider.log.service;

import java.io.File;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.PostConstruct;

import com.unittec.zk.provider.log.common.LogServiceIdConstants;
import com.unittec.zk.provider.log.common.RedisKeyConstants;
import com.unittec.zk.provider.log.entity.SysLogEntity;
import com.unittec.zk.provider.log.mapper.SysLogMapper;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Service;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.unittec.zk.provider.common.Md5;
import com.unittec.zk.provider.common.RedisUtils;
import com.unittec.zk.provider.common.ServiceIdConstants;
import com.unittec.zk.provider.root.ServiceRoot;
import com.unittec.zk.provider.scope.ThreadLocalScope;
import com.unittec.zk.provider.service.HystrixRestTemplateService;
import com.unittec.zk.sdk.auth.request.Req101106;
import com.unittec.zk.sdk.auth.response.Res101106;
import com.unittec.zk.sdk.log.request.Req102011;
import com.unittec.zk.sdk.log.request.Req102013;
import com.unittec.zk.sdk.log.request.Req102014;
import com.unittec.zk.sdk.log.request.Req102017;
import com.unittec.zk.sdk.log.request.Req102018;
import com.unittec.zk.sdk.log.response.Res102014;
import com.unittec.zk.sdk.log.response.Res102015;
import com.unittec.zk.sdk.log.response.Res102020;
import com.unittec.zk.sdk.root.common.FieldConstants;
import com.unittec.zk.sdk.root.domain.GatewayRequest;
import com.unittec.zk.sdk.root.domain.Result;
import com.unittec.zk.sdk.root.enums.LogLevelE;

import ch.qos.logback.classic.Logger;

@Service
public class LogService extends ServiceRoot<SysLogMapper, SysLogEntity> {

    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(LogService.class);

    @Autowired
    HystrixRestTemplateService hystrixRestTemplateService;

    private static final String REDIS_LOGLEVEL_KEY = "loglevel_";

    private static final String REDIS_LOGNAME_KEY = "logname";

    // 为了性能考虑，各个应用对应的业务日志级别存放在redis，同时本地的hash变量中也放一份
    private static final Map<String, LogLevelE> appLogLevelMap = new ConcurrentHashMap<>();

    private static final Map<String, String> appLogNameMap = new ConcurrentHashMap<>();

    @Autowired
    private RedisUtils redisUtils;

    private volatile boolean loaded = false;

    // 日志服务启动成功，需要将各个服务的日志级别同步到各个服务和刷新本地的缓存
    @PostConstruct
    public void init() {
        if (loaded) {
            return;
        }
        try {
            Set<String> keys = redisUtils.keys(REDIS_LOGLEVEL_KEY);
            for (String key : keys) {
                key = key.substring(key.indexOf(REDIS_LOGLEVEL_KEY));
                appLogLevelMap.put(key, LogLevelE.convertLevel(redisUtils.get(key)));
            }
            keys = redisUtils.keys(REDIS_LOGNAME_KEY);
            for (String key : keys) {
                key = key.substring(key.indexOf(REDIS_LOGNAME_KEY));
                appLogNameMap.put(key, redisUtils.get(key));
            }
            loaded = true;
        } catch (Exception e) {
            LOGGER.error("init error", e);
        }
    }

    /**
     * 通知对应的服务模块,刷新日志级别
     *
     * @param req
     */
    private void notifyServiceRefreshLogLevel(Req102011 req) {
        ThreadLocalScope.AuthData authData = ThreadLocalScope.getAuthData();
        GatewayRequest<Req102011> req102011 = new GatewayRequest<>();
        req102011.setContent(req);
        req102011.setAppId(req.getAppId());
        req102011.setRequestId(authData.getRequestId());
        try {
            hystrixRestTemplateService.exchange(Md5.getMd5(FieldConstants.SYS_REQUEST_SOURCE_ID),
                ServiceIdConstants.APP_MSG_SERVICE_102011, req102011, authData,
                new ParameterizedTypeReference<Result<String>>() {});
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 设置日志级别
     *
     * @param req
     */
    public void setLogLevel(Req102011 req) {
        String serviceId = req.getServiceId();
        String appId = req.getAppId();
        if (StringUtils.isBlank(serviceId) || StringUtils.isBlank(appId)) {
            throw new RuntimeException("参数为空");
        }
        appLogLevelMap.put(REDIS_LOGLEVEL_KEY + serviceId + ":" + appId, req.getLogLevel());
        redisUtils.set(REDIS_LOGLEVEL_KEY + serviceId + ":" + appId, req.getLogLevel().getMsg());
        notifyServiceRefreshLogLevel(req);
    }

    /**
     * 应用创建日志名
     *
     * @param req
     * @return
     */
    public boolean createLog(Req102011 req) {
        if (null == req.getLogLevel()) {
            req.setLogLevel(LogLevelE.ERROR);
        }
        if (appLogNameMap.containsValue(req.getLogName())) {
            return false;
        }
        appLogNameMap.put(REDIS_LOGNAME_KEY + ":" + req.getAppId(), req.getLogName());
        redisUtils.set(REDIS_LOGNAME_KEY + ":" + req.getAppId(), req.getLogName());
        return true;
    }

    /**
     * 获取应用 日志名
     *
     * @param appId
     * @return
     */
    public String getLogName(String appId) {
        String name = appLogNameMap.get(REDIS_LOGNAME_KEY + ":" + appId);
        if (StringUtils.isBlank(name)) {
            name = redisUtils.get(REDIS_LOGNAME_KEY + ":" + appId);
            if (!StringUtils.isBlank(name)) {
                appLogNameMap.put(REDIS_LOGNAME_KEY + ":" + appId, name);
            }
        }
        return name;
    }

    /**
     * 获取日志级别
     *
     * @param serviceId
     *            应用服务ID
     * @param appId
     *            应用ID
     * @return
     */
    private LogLevelE getLogLevel(String serviceId, String appId) {
        // 如果系统启动时加载redis出错,重新再加载一次，防止应用记录日志失败
        if (!loaded) {
            init();
        }
        return appLogLevelMap.get(REDIS_LOGLEVEL_KEY + serviceId + ":" + appId);
    }

    public LogLevelE getLogLevel(Req102011 req) {
        return getLogLevel(req.getServiceId(), req.getAppId());
    }

    public Res102014 getLogInfo(Req102011 req) {
        Res102014 res = new Res102014();
        res.setServiceId(req.getServiceId());
        res.setLogLevel(getLogLevel(req));
        res.setAppId(req.getAppId());
        res.setLogName(getLogName(res.getAppId()));
        return res;
    }

    public LogLevelE getLogLevel(Req102013 req) {
        return getLogLevel(req.getServiceId(), req.getAppId());
    }

    /**
     * 业务服务获取所有应用对应的日志级别
     */
    public List<Res102015> getLogLevels(String serviceId) {
        List<Res102015> list = new ArrayList<>();
        Res102015 res = null;
        Set<String> keys = redisUtils.keys(REDIS_LOGLEVEL_KEY + serviceId);
        for (String key : keys) {
            res = new Res102015();
            res.setServiceId(serviceId);
            res.setLogLevel(LogLevelE.convertLevel(redisUtils.get(key)));
            res.setAppId(key.substring(key.lastIndexOf(":") + 1));
            res.setLogName(getLogName(res.getAppId()));
            list.add(res);
        }
        return list;
    }

    /**
     * 根据应用appID 获取 各个服务的日志级别
     *
     * @param appId
     * @return
     */
    public List<Res102020> getLogLevelsByAppId(String appId) {
        if (StringUtils.isBlank(appId)) {
            return new ArrayList<>();
        }
        List<Res102020> list = new ArrayList<>();
        Res102020 res = null;
        if (appLogLevelMap == null) {
            Set<String> keys = redisUtils.keys(REDIS_LOGLEVEL_KEY);
            for (String key : keys) {
                key = key.substring(key.indexOf(REDIS_LOGLEVEL_KEY));
                appLogLevelMap.put(key, LogLevelE.convertLevel(redisUtils.get(key)));
            }
        }

        for (Map.Entry<String, LogLevelE> map : appLogLevelMap.entrySet()) {
            String key = map.getKey();
            if (StringUtils.isBlank(key)) {
                continue;
            }
            key = key.replace(REDIS_LOGLEVEL_KEY, "");
            String[] split = key.split(":");
            if (split.length != 2) {
                continue;
            }
            String serviceId = split[0];
            if ("null".equals(serviceId)) {
                continue;
            }
            String newAppId = split[1];
            if (!appId.equals(newAppId)) {
                continue;
            }
            res = new Res102020();
            res.setServiceId(serviceId);
            res.setAppId(appId);
            res.setLogName(map.getValue().getMsg());
            res.setLogLevel(LogLevelE.convertLevel(map.getValue().getMsg()));
            list.add(res);
        }
        return list;
    }

    /**
     * @param req
     */
    public void log(Req102013 req) {
        String appId = req.getAppId();
        if (StringUtils.isBlank(appId)) {
            return;
        }
        String logName = getLogName(appId);
        if (StringUtils.isBlank(logName)) {
            logName = appId;
        }
        Logger log = LoggerBuilder.getLogger(logName);
        if (null != log) {
            log.error("requestid=[{}] requestime=[{}] level=[{}] serviceId=[{}] method=[{}] content=[{}] error=[{}]",
                req.getRequestId(), req.getReqTime(), req.getLogLevel().getMsg(), req.getServiceId(), req.getMethod(),
                req.getLogContent(), req.getErrMsg());
        }
    }

    /**
     * @param req
     */
    public void log(Req102014 req) {
        Logger log = LoggerBuilder.getLogger("syslog");
        if (null != log) {
            log.error("requestime=[{}] level=[{}] serviceId=[{}] method=[{}] content=[{}] error=[{}]", req.getReqTime(),
                req.getLogLevel().getMsg(), req.getServiceId(), req.getMethod(), req.getLogContent(), req.getErrMsg());
        }
    }

    /**
     * @param req
     */
    public void aopMethodlog(Req102014 req) {
        Logger log = LoggerBuilder.getLogger("sysAopMethodLog");
        if (null != log) {
            log.error("requesId=[{}]  requestime=[{}] level=[{}] serviceId=[{}] method=[{}] content=[{}] error=[{}]",
                req.getRequestId(), req.getReqTime(), req.getLogLevel().getMsg(), req.getServiceId(), req.getMethod(),
                req.getLogContent(), req.getErrMsg());
        }
    }

    /**
     * @param req
     */
    public void addOperateLog(Req102017 req) {
        try {
            String userName = "";
            String relaName = "";
            ThreadLocalScope.setAuthData(req.getRequestId(), req.getToken(), req.getAppId(), req.getTenantId(),
                req.getIp(), req.getUser(), req.getAppCode(), req.getUserName());
            String userId = req.getUser();
            if (StringUtils.isNotEmpty(userId)) {
                userName = getUserName(userId);
            }
            if (StringUtils.isNotEmpty(userId)) {
                relaName = getRelaName(userId);
            }
            SysLogEntity entity = new SysLogEntity();
            entity.setIp(req.getIp());
            entity.setLogType(req.getLogType());
            entity.setLogTitle(req.getLogTitle());
            entity.setLogContent(req.getLogContent());
            entity.setOperateType(req.getOperateType());
            entity.setOperateStatus(req.getOperateStatus());
            entity.setUserId(req.getUser());
            entity.setUserName(userName);
            entity.setRelaName(relaName);
            entity.setMethod(req.getMethod());
            entity.setRequestUrl(req.getRequestUrl());
            entity.setRequestParam(req.getRequestParam());
            entity.setRequestType(req.getRequestType());
            entity.setCostTime(req.getCostTime());
            super.save(entity);
        } catch (Exception e) {
            LOGGER.error("addOperateLog", e);
        }
    }

    /**
     * 获取用户的账号
     *
     * @param userId
     * @return
     */
    private String getUserName(String userId) {
        String userName = "";
        if (StringUtils.isNotEmpty(userId)) {
            String key = RedisKeyConstants.USER_ID_KEY + userId;
            userName = redisUtils.get(key);
            if (StringUtils.isBlank(userName)) {
                GatewayRequest<Req101106> getWay = new GatewayRequest<>();
                Req101106 req101106 = new Req101106();
                req101106.setId(userId);
                getWay.setMethod("101106");
                getWay.setContent(req101106);
                Result<Res101106> result101106 = hystrixRestTemplateService.exchange(
                    Md5.getMd5(FieldConstants.SYS_REQUEST_SOURCE_ID), LogServiceIdConstants.AUTH_101106, getWay,
                    ThreadLocalScope.getAuthData(), new ParameterizedTypeReference<Result<Res101106>>() {});
                if (result101106.isSuccess()) {
                    String userNameDb = result101106.getData().getUsername();
                    if (StringUtils.isNotBlank(userNameDb)) {
                        userName = userNameDb;
                        redisUtils.set(key, userNameDb, 1000 * 60 * 60 * 24L);
                    }
                }
            }
        }

        return userName;
    }

    /**
     * 获取用户的真实姓名
     *
     * @param userId
     * @return
     */
    private String getRelaName(String userId) {
        String relaName = "";
        if (StringUtils.isNotEmpty(userId)) {
            String key = RedisKeyConstants.RELA_NAME_KEY + userId;
            relaName = redisUtils.get(key);
            if (StringUtils.isBlank(relaName)) {
                GatewayRequest<Req101106> getWay = new GatewayRequest<>();
                Req101106 req101106 = new Req101106();
                req101106.setId(userId);
                getWay.setMethod("101106");
                getWay.setContent(req101106);
                Result<Res101106> result101106 = hystrixRestTemplateService.exchange(
                    Md5.getMd5(FieldConstants.SYS_REQUEST_SOURCE_ID), LogServiceIdConstants.AUTH_101106, getWay,
                    ThreadLocalScope.getAuthData(), new ParameterizedTypeReference<Result<Res101106>>() {});
                if (result101106.isSuccess()) {
                    String relaNameDb = result101106.getData().getRealname();
                    if (StringUtils.isNotBlank(relaNameDb)) {
                        relaName = relaNameDb;
                        redisUtils.set(key, relaNameDb, 1000 * 60 * 60 * 24L);
                    }
                }
            }
        }

        return relaName;
    }

    public IPage<Map<String, Object>> logListForPage(Req102018 req102018) {
        // 封装查询条件
        Map<String, Object> paramMap = new HashMap<>();
        if (null != req102018.getBeginCreateTime()) {
            paramMap.put("beginCreateTime", req102018.getBeginCreateTime());
        }
        if (null != req102018.getEndCreateTime()) {
            paramMap.put("endCreateTime", req102018.getEndCreateTime());
        }
        if (StringUtils.isNotBlank(req102018.getSelectOne())) {
            paramMap.put("selectOne", req102018.getSelectOne());
        }
        if (null != req102018.getLogType()) {
            paramMap.put("logType", req102018.getLogType());
        }
        if (StringUtils.isNotBlank(req102018.getOperateType())) {
            paramMap.put("operateType", req102018.getOperateType());
        }
        if (StringUtils.isNotBlank(req102018.getOperateStatus())) {
            paramMap.put("operateStatus", req102018.getOperateStatus());
        }
        if (StringUtils.isNotBlank(req102018.getLogContent())) {
            paramMap.put("logContent", req102018.getLogContent());
        }
        if (StringUtils.isNotBlank(req102018.getLogTitle())) {
            paramMap.put("logTitle", req102018.getLogTitle());
        }
        if (StringUtils.isNotBlank(req102018.getUserName())) {
            paramMap.put("userName", req102018.getUserName());
        }
        if (StringUtils.isNotBlank(req102018.getRelaName())) {
            paramMap.put("relaName", req102018.getRelaName());
        }

        /*paramMap.put("endStartTime", req102018.getEndStartTime());
        paramMap.put("endEndTime", req102018.getEndEndTime());
        paramMap.put("cusId", req102018.getCusId());
        paramMap.put("payType", req102018.getPayType());*/
        return getMapper().logListForPage(new Page<>(req102018.getCurrent(), req102018.getSize()), paramMap);
    }

    /**
     * 上传app 日志
     *
     * @param name
     * @param bytes
     */
    public void uploadAppLog(String name, byte[] bytes) {
        createFilePath(name, bytes);
    }

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    private void createFilePath(String baseFileUrl, byte[] bytes) {
        String property = System.getProperty("user.dir");
        // String property = System.getProperty("log.path");
        try {
            property = property + File.separator + "logs";
            File file4 = new File(property);
            if (!file4.exists()) {
                if (!file4.mkdir()) {
                    return;
                }
            }
            LocalDate now = LocalDate.now();
            String format = now.format(DATE_TIME_FORMATTER);
            property = property + File.separator + "app";
            File file = new File(property);
            if (!file.exists()) {
                if (!file.mkdir()) {
                    return;
                }
            }
            property = property + File.separator + format;
            File file2 = new File(property);
            if (!file2.exists()) {
                if (!file2.mkdir()) {
                    return;
                }
            }
            baseFileUrl = baseFileUrl.replaceAll(" +", "");
            if (baseFileUrl.contains("/")) {
                baseFileUrl = baseFileUrl.substring(baseFileUrl.lastIndexOf("/") + 1);
            }
            String logFileName = baseFileUrl + "_" + System.currentTimeMillis() + ".log";
            File file3 = new File(property + File.separator + logFileName);
            FileUtils.writeByteArrayToFile(file3, bytes);
        } catch (Exception e) {
            LOGGER.error("addOperateLog", e);
        }
    }
}
